# -*- coding: utf-8 -*-
from typing import Dict, Any, Tuple, Optional, Deque
import numpy as np
import time
from collections import deque

from recognition.actions.base import BaseAction
from common.landmarks import LMS
from common.config import VIS_THRESH


MIN_RISE   = 0.20   # 最小上升幅度（肩宽）
MIN_FALL   = 0.12   # 最小回落幅度（肩宽）
VEL_UP     = -0.9   # 上升速度阈值（负，肩宽/秒）
VEL_DOWN   =  0.9   # 回落速度阈值（正，肩宽/秒）
DEADZONE   = 0.015  # 微动死区（肩宽）
EMA_T      = 0.08   # EMA 时间常数（秒）
MED_WIN    = 3      # 中值滤波窗口（帧）
WINDOW_MAX = 1.0    # 最大时长（秒）
MIN_RISE_T = 0.12   # 上升最短时长（秒）
MIN_FALL_T = 0.12   # 回落最短时长（秒）
COOLDOWN   = 1.0    # 冷却（秒）

def _shoulder_width(l_sh, r_sh) -> Optional[float]:
    if getattr(l_sh, "visibility", 0.0) >= VIS_THRESH and getattr(r_sh, "visibility", 0.0) >= VIS_THRESH:
        sw = abs(r_sh.x - l_sh.x)
        if sw and sw > 1e-6:
            return float(sw)
    return None

def _avg_hip_y(l_hp, r_hp, vis_thresh=VIS_THRESH) -> Optional[float]:
    vals = []
    if getattr(l_hp, "visibility", 0.0) >= vis_thresh: vals.append(l_hp.y)
    if getattr(r_hp, "visibility", 0.0) >= vis_thresh: vals.append(r_hp.y)
    return float(np.mean(vals)) if vals else None

class _HipRFSM:
    """
    Hip Rise-Fall State Machine（仅髋）：
      idle -> rising(上升) -> falling(回落) -> event
    观测：髋的 y（向下为正），上升= y 减小；回落= y 增大。
    使用肩宽归一、EMA 平滑与微动死区。
    """
    __slots__ = (
        "state", "last_y", "last_t", "y_ema",
        "y_min", "y_start", "rise_t0", "fall_t0",
        "cooldown_until", "sum_v_up", "cnt_up", "sum_v_down", "cnt_down"
    )

    def __init__(self):
        self.state = "idle"
        self.last_y: Optional[float] = None
        self.last_t: Optional[float] = None
        self.y_ema: Optional[float] = None
        self.y_min: Optional[float] = None
        self.y_start: Optional[float] = None
        self.rise_t0: Optional[float] = None
        self.fall_t0: Optional[float] = None
        self.cooldown_until: float = 0.0
        self.sum_v_up = 0.0; self.cnt_up = 0
        self.sum_v_down = 0.0; self.cnt_down = 0

    def reset(self):
        self.__init__()

    def update(self, t_now: float, hip_y_sw: float) -> Optional[Dict[str, Any]]:
        # 冷却中：仅维持 EMA 与时间
        if self.last_y is None or self.last_t is None or self.y_ema is None:
            self.last_y, self.last_t, self.y_ema = hip_y_sw, t_now, hip_y_sw
            return None

        dt = max(1e-3, t_now - self.last_t)
        # EMA 平滑
        alpha = 1.0 - np.exp(-dt / max(1e-3, EMA_T))
        self.y_ema = (1 - alpha) * self.y_ema + alpha * hip_y_sw

        # 速度（y 向下为正；上升=负）
        dy = self.y_ema - self.last_y
        if abs(dy) < DEADZONE:
            dy = 0.0
        v = dy / dt

        if t_now < self.cooldown_until:
            self.last_y, self.last_t = self.y_ema, t_now
            return None

        if self.state == "idle":
            # 观察到上升趋势
            if v < VEL_UP * 0.5:  # 放宽触发门槛
                self.state = "rising"
                self.y_start = self.y_ema
                self.y_min = self.y_ema
                self.rise_t0 = t_now
                self.sum_v_up = 0.0; self.cnt_up = 0
                self.sum_v_down = 0.0; self.cnt_down = 0

        elif self.state == "rising":
            self.y_min = min(self.y_min, self.y_ema)  # y 越小表示越上升
            self.sum_v_up += v; self.cnt_up += 1
            rise_t = t_now - (self.rise_t0 or t_now)
            rise_amount = (self.y_start - self.y_min)  # 上升幅度（向上为正）

            # 上升满足幅度 + 上升段平均速度足够负 + 最短上升时长
            # 且速度转正（到达顶点开始回落）时进入 falling
            if v >= 0.0 and rise_amount >= MIN_RISE and (self.sum_v_up / max(1, self.cnt_up)) <= VEL_UP and rise_t >= MIN_RISE_T:
                self.state = "falling"
                self.fall_t0 = t_now
            # 超时保护
            if rise_t > WINDOW_MAX:
                self.state = "idle"

        elif self.state == "falling":
            fall_t = t_now - (self.fall_t0 or t_now)
            fall_amount = (self.y_ema - self.y_min)  # 回落幅度（向下为正）
            self.sum_v_down += v; self.cnt_down += 1
            avg_v_down = self.sum_v_down / max(1, self.cnt_down)

            if fall_amount >= MIN_FALL and avg_v_down >= VEL_DOWN and fall_t >= MIN_FALL_T and (t_now - (self.rise_t0 or t_now)) <= WINDOW_MAX:
                evt = {
                    "rise": float(self.y_start - self.y_min),
                    "fall": float(fall_amount),
                    "dur": float(t_now - (self.rise_t0 or t_now))
                }
                self.state = "idle"
                self.cooldown_until = t_now + COOLDOWN
                self.last_y, self.last_t = self.y_ema, t_now
                return evt

            if (t_now - (self.rise_t0 or t_now)) > WINDOW_MAX:
                self.state = "idle"

        self.last_y, self.last_t = self.y_ema, t_now
        return None


class JumpAction(BaseAction):
    label = "jump"

    def __init__(self):
        super().__init__()
        self._med_buf: Dict[int, Deque[float]] = {}

    def score(self, lms, person_id: int, get_state) -> Tuple[float, Dict[str, Any]]:
        st = get_state(person_id)
        sm: _HipRFSM = st.setdefault("_hip_rf_sm", _HipRFSM())

        def ok(p): return getattr(p, "visibility", 0.0) >= max(0.6, VIS_THRESH)

        l_sh = lms[LMS.LEFT_SHOULDER]; r_sh = lms[LMS.RIGHT_SHOULDER]
        l_hp = lms[LMS.LEFT_HIP]; r_hp = lms[LMS.RIGHT_HIP]

        # 肩宽可见
        if not (ok(l_sh) and ok(r_sh)):
            return 0.0, {}
        sw = _shoulder_width(l_sh, r_sh)
        if sw is None:
            return 0.0, {}

        # 髋 y（至少一侧）
        hip_y = _avg_hip_y(l_hp, r_hp)
        if hip_y is None:
            return 0.0, {}

        # 归一：单位为肩宽
        hip_y_sw = hip_y / sw

        # 中值滤波
        buf = self._med_buf.setdefault(person_id, deque(maxlen=MED_WIN))
        buf.append(hip_y_sw)
        hip_y_sw_med = float(np.median(buf)) if len(buf) >= 2 else hip_y_sw

        t_now = time.time()
        evt = sm.update(t_now, hip_y_sw_med)
        if evt is not None:
            # 事件分：0.7~1.0，依据 rise 的相对幅度归一
            rise = evt["rise"]
            s = max(0.0, min(1.0, rise / (MIN_RISE * 1.6)))
            return 0.7 + 0.3 * s, {"rise": rise, "fall": evt["fall"], "dur": evt["dur"], "state": "event"}

        # 连续分：根据最近阶段给低分，避免静止高分
        # 简单：用绝对变化速率的有界映射
        # 注意：这里没有直接取 v，因 v 在状态机里；仅提供保守低分
        cont = 0.0
        return cont, {"state": sm.state}